﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Vds2465Telegrams;

namespace Vds2465Parser
{
    internal static class GenParse_UserData_26_BlockStateAll
    {
        #region private constants

        /// <summary>
        /// All bytes including the AddressAddOn
        /// </summary>
        private const int NrOfHeaderBytes = 5;

        #endregion private constants

        #region internal methods

        internal static byte[] GenerateBytes(UserData userData)
        {
            byte[] userDataBytes = null;

            if (userData is UserData_26_BlockStateAll)
            {
                UserData_26_BlockStateAll userData26 = userData as UserData_26_BlockStateAll;
                byte[] inputBytes = userData26.GetCopyOfInputBytes();
                byte[] outputBytes = userData26.GetCopyOfOutputBytes();
                byte[] disruptionBytes = userData26.GetCopyOfDisruptionBytes();

                // calculate number of bytes
                int totalNrOfBytes = NrOfHeaderBytes + 6; // 2 header bytes for each byte array
                if (inputBytes != null)
                {
                    totalNrOfBytes += inputBytes.Length;                    
                }
                if (outputBytes != null)
                {
                    totalNrOfBytes += outputBytes.Length;                    
                }
                if (disruptionBytes != null)
                {
                    totalNrOfBytes += disruptionBytes.Length;
                }

                if (totalNrOfBytes > 255)
                {
                    // TODO handle error
                    // TODO is 255 the correct number or do we need a smaller number
                }

                userDataBytes = new byte[totalNrOfBytes];
                userDataBytes[0] = (byte) (totalNrOfBytes - GenParse_UserData.HeaderLength);	// user data length
                userDataBytes[1] = UserData.UserData26BlockStateAll;
                userDataBytes[2] = userData26.Device;
                userDataBytes[3] = userData26.Address;
                userDataBytes[4] = userData26.AddressAddOn;                
                int currentIndex = 5;
                HandleGeneratingBlockBytes(userDataBytes, GenParse_AddressExtension.GenerateByte_AddExt_1(eAddressExtension_1.Input), ref currentIndex, inputBytes);
                HandleGeneratingBlockBytes(userDataBytes, GenParse_AddressExtension.GenerateByte_AddExt_1(eAddressExtension_1.Output), ref currentIndex, outputBytes);
                HandleGeneratingBlockBytes(userDataBytes, GenParse_AddressExtension.GenerateByte_AddExt_1(eAddressExtension_1.Disruption), ref currentIndex, disruptionBytes);                
            }
            else
            {
                // TODO handle error
            }

            return userDataBytes;
        }

        internal static bool ParseBytes(byte[] userDataBytes, out UserData userData26BlockStateAll, out int consumedBytes)
        {
            userData26BlockStateAll = null;
            consumedBytes = 0;

            if (userDataBytes.Length >= NrOfHeaderBytes)
            {
				byte setLength = userDataBytes[0];
				if (userDataBytes.Length >= setLength + 2)
				{
					if (userDataBytes[1] == UserData.UserData26BlockStateAll)
					{
						byte device = userDataBytes[2];
						byte address = userDataBytes[3];
						byte addressAddOn = userDataBytes[4];

						int currentIndex = 5;

						byte[] inputBytes = null;
						byte[] outputBytes = null;
						byte[] disruptionBytes = null;
                    
						int nrOfBlocksFound = 0;
						eAddressExtension_1 addressExtension;
						byte[] blockBytes;
                    
						while ((nrOfBlocksFound < 3) && (currentIndex < userDataBytes.Length -1))
						{
							// we accept an arbitrary order of AddressExtensions

							bool bRetVal = HandleParseBlockBytes(userDataBytes, ref currentIndex, out addressExtension, out blockBytes);
							if (bRetVal)
							{
								nrOfBlocksFound++;
								switch (addressExtension)
								{
									case eAddressExtension_1.Input:
										CheckAndCopyBytes(blockBytes, ref inputBytes);
										break;
									case eAddressExtension_1.Output:
										CheckAndCopyBytes(blockBytes, ref outputBytes);
										break;
									case eAddressExtension_1.Disruption:
										CheckAndCopyBytes(blockBytes, ref disruptionBytes);
										break;
									default:
										// TODO handle error - unknown value for eAddressExtension_1
										break;
								}
							}
							else
							{
								// TODO handle error
								return false;
							}
						}
                    
						userData26BlockStateAll = new UserData_26_BlockStateAll(device, address, addressAddOn, inputBytes, outputBytes, disruptionBytes);
						consumedBytes = currentIndex;
						return true;
					}

					// TODO handle error: wrong value for the data type
					return false;
				}

				// TODO handle error: not enough bytes as specified by the SL value
				return false;
			}

            // TODO handle error (not enough bytes)
            return false;
        }

        #endregion internal methods

        #region private methods

        internal static void HandleGeneratingBlockBytes(byte[] userDataBytes, byte addressExtensionByte, ref int currentIndex, byte[] bytes2Copy)
        {
            userDataBytes[currentIndex] = addressExtensionByte;
            currentIndex++;
            if (bytes2Copy != null)
            {
                // TODO how handle this check
                if (bytes2Copy.Length < 255)
                {
                    userDataBytes[currentIndex] = (byte)bytes2Copy.Length;
                    currentIndex++;
                    for (int index = 0; index < bytes2Copy.Length; index++)
                    {
                        userDataBytes[currentIndex + index] = bytes2Copy[index];
                    }

                    currentIndex += bytes2Copy.Length;
                }
                else
                {
                    // TODO handle error
                }
            }
            else
            {
                userDataBytes[currentIndex] = 0;
                currentIndex++;
            }
        }

        internal static bool HandleParseBlockBytes(byte[] userDataBytes, ref int currentIndex, out eAddressExtension_1 addressExtension, out byte[] blockBytes)
        {
            // set default values
            addressExtension = eAddressExtension_1.Input;
            blockBytes = null;

            if (userDataBytes.Length > currentIndex + 1)
            {
                // yes we have two more bytes: one for the AddressExtension value and one for the block length
                byte addressExtensionByte = userDataBytes[currentIndex];
                bool bRetVal = GenParse_AddressExtension.ParseByte_AddExt_1(addressExtensionByte, out addressExtension);
                if (bRetVal)
                {
                    currentIndex++;
                    byte blockLength = userDataBytes[currentIndex];
                    currentIndex++;
                    blockBytes = new byte[blockLength];
                    for (int index = 0; index < blockLength; index++)
                    {
                        blockBytes[index] = userDataBytes[currentIndex];
                        currentIndex++;
                    }

                    return true;
                }
                else
                {
                    // TODO handle error
                    return false;
                }
            }

            return false;
        }

        internal static void CheckAndCopyBytes(byte[] sourceBytes, ref byte[] targetBytes)
        {
            if (targetBytes == null)
            {
                if (sourceBytes != null)
                {
                    targetBytes = new byte[sourceBytes.Length];
                    for (int index = 0; index < sourceBytes.Length; index++)
                    {
                        targetBytes[index] = sourceBytes[index];
                    }
                }
                else
                {
                    targetBytes = new byte[0];
                }
            }
            else
            {
                // TODO handle error
                // these bytes are already set...
            }
        }

        #endregion private methods
    }
}
